package io.lvdaxian.mybatis08.reflection;

import io.lvdaxian.mybatis08.reflection.invoker.GetFieldInvoker;
import io.lvdaxian.mybatis08.reflection.invoker.Invoker;
import io.lvdaxian.mybatis08.reflection.invoker.MethodInvoker;
import io.lvdaxian.mybatis08.reflection.property.PropertyTokenizer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

/**
 * 表示 元类
 *
 * @author lihh
 */
public class MetaClass {
  
  private Reflector reflector;
  
  private MetaClass(Class<?> clazz) {
    this.reflector = Reflector.forClass(clazz);
  }
  
  /**
   * 这是一个 生成类实例的方法
   *
   * @param type class type
   * @return 元信息
   * @author lihh
   */
  public static MetaClass forClass(Class<?> type) {
    return new MetaClass(type);
  }
  
  public static boolean isClassCacheEnabled() {
    return Reflector.isClassCacheEnabled();
  }
  
  public static void setClassCacheEnabled(boolean classCacheEnabled) {
    Reflector.setClassCacheEnabled(classCacheEnabled);
  }
  
  public MetaClass metaClassForProperty(String name) {
    Class<?> propType = reflector.getGetterType(name);
    return MetaClass.forClass(propType);
  }
  
  /**
   * 根据名称 寻找属性
   *
   * @param name 名称
   * @return 返回寻找结果
   * @author lihh
   */
  public String findProperty(String name) {
    StringBuilder prop = buildProperty(name, new StringBuilder());
    return prop.length() > 0 ? prop.toString() : null;
  }
  
  /**
   * 寻找属性的 驼峰转换
   *
   * @param name                对应的名称
   * @param useCamelCaseMapping 驼峰格式
   * @return 转换后的字符串
   * @author lihh
   */
  public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
      name = name.replace("_", "");
    }
    return findProperty(name);
  }
  
  public String[] getGetterNames() {
    return reflector.getGetablePropertyNames();
  }
  
  public String[] getSetterNames() {
    return reflector.getSetablePropertyNames();
  }
  
  public Class<?> getSetterType(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaClass metaProp = metaClassForProperty(prop.getName());
      return metaProp.getSetterType(prop.getChildren());
    } else {
      return reflector.getSetterType(prop.getName());
    }
  }
  
  public Class<?> getGetterType(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      MetaClass metaProp = metaClassForProperty(prop);
      return metaProp.getGetterType(prop.getChildren());
    }
    // issue #506. Resolve the type inside a Collection Object
    return getGetterType(prop);
  }
  
  private MetaClass metaClassForProperty(PropertyTokenizer prop) {
    Class<?> propType = getGetterType(prop);
    return MetaClass.forClass(propType);
  }
  
  private Class<?> getGetterType(PropertyTokenizer prop) {
    Class<?> type = reflector.getGetterType(prop.getName());
    if (prop.getIndex() != null && Collection.class.isAssignableFrom(type)) {
      Type returnType = getGenericGetterType(prop.getName());
      if (returnType instanceof ParameterizedType) {
        Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
        if (actualTypeArguments != null && actualTypeArguments.length == 1) {
          returnType = actualTypeArguments[0];
          if (returnType instanceof Class) {
            type = (Class<?>) returnType;
          } else if (returnType instanceof ParameterizedType) {
            type = (Class<?>) ((ParameterizedType) returnType).getRawType();
          }
        }
      }
    }
    return type;
  }
  
  private Type getGenericGetterType(String propertyName) {
    try {
      Invoker invoker = reflector.getGetInvoker(propertyName);
      if (invoker instanceof MethodInvoker) {
        Field _method = MethodInvoker.class.getDeclaredField("method");
        _method.setAccessible(true);
        Method method = (Method) _method.get(invoker);
        return method.getGenericReturnType();
      } else if (invoker instanceof GetFieldInvoker) {
        Field _field = GetFieldInvoker.class.getDeclaredField("field");
        _field.setAccessible(true);
        Field field = (Field) _field.get(invoker);
        return field.getGenericType();
      }
    } catch (NoSuchFieldException | IllegalAccessException ignored) {
    }
    return null;
  }
  
  public boolean hasSetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      if (reflector.hasSetter(prop.getName())) {
        MetaClass metaProp = metaClassForProperty(prop.getName());
        return metaProp.hasSetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      return reflector.hasSetter(prop.getName());
    }
  }
  
  public boolean hasGetter(String name) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    if (prop.hasNext()) {
      if (reflector.hasGetter(prop.getName())) {
        MetaClass metaProp = metaClassForProperty(prop);
        return metaProp.hasGetter(prop.getChildren());
      } else {
        return false;
      }
    } else {
      return reflector.hasGetter(prop.getName());
    }
  }
  
  public Invoker getGetInvoker(String name) {
    return reflector.getGetInvoker(name);
  }
  
  public Invoker getSetInvoker(String name) {
    return reflector.getSetInvoker(name);
  }
  
  /**
   * 构建 buffer 属性
   *
   * @param name    key 名称
   * @param builder buffer 构建器
   * @return 返回 string buffer
   * @author lihh
   */
  private StringBuilder buildProperty(String name, StringBuilder builder) {
    PropertyTokenizer prop = new PropertyTokenizer(name);
    
    if (prop.hasNext()) {
      String propertyName = reflector.findPropertyName(prop.getName());
      if (propertyName != null) {
        builder.append(propertyName);
        builder.append(".");
        MetaClass metaProp = metaClassForProperty(propertyName);
        metaProp.buildProperty(prop.getChildren(), builder);
      }
    } else {
      String propertyName = reflector.findPropertyName(name);
      if (propertyName != null) {
        builder.append(propertyName);
      }
    }
    
    return builder;
  }
}
